package com.cgs.query.service.impl

import com.cgs.query.config.RunnerJdbcTemplateAware
import com.cgs.query.domain.*
import com.cgs.query.event.Project2DefineEvent
import com.cgs.query.event.QueryProjectEvent
import com.cgs.query.exception.QueryException
import com.cgs.query.handler.*
import com.cgs.query.service.*
import com.cgs.query.service.jpa.QueryProjectRepository
import com.cgs.query.util.GsonUtils
import com.cgs.query.util.Utils
import mu.KotlinLogging
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationEventPublisher
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.stereotype.Service
import org.springframework.util.CollectionUtils
import org.springframework.web.multipart.MultipartFile
import java.io.InputStreamReader

private val logger = KotlinLogging.logger { }

@Service
open class QueryProjectServiceImpl : IQueryProjectService, UserInfoHolderAware, SecurityAdapter, RunnerJdbcTemplateAware {

    @Autowired
    private lateinit var publisher: ApplicationEventPublisher
    @Autowired
    private lateinit var repository: QueryProjectRepository
    @Autowired
    private lateinit var querySourceService: IQuerySourceService
    @Autowired
    private lateinit var project2DefineService: IProject2DefineService
    @Autowired
    private lateinit var queryDefineService: IQueryDefineService
    @Autowired
    private lateinit var executionObjService: IExecutionObjService

    private lateinit var jdbcTemplate: JdbcTemplate
    private lateinit var userHolder: UserInfoHolder

    override fun insert(project: QueryProject): QueryProject {
        return if (repository.exists(project.name))
            throw QueryException("项目名已存在:${project.name}")
        else
            repository.save(project.apply { userId = userHolder.getUserId() })
    }

    @Permission
    override fun save(project: QueryProject): QueryProject {
        val oldProject = repository.findOne(project.name).copy()
        val newProject = repository.save(project)
        publisher.publishEvent(QueryProjectEvent(project.name, QueryProjectEvent.Type.UPDATE).apply {
            userId = userHolder.getUserId()
            newQueryProject = newProject
            originQueryProject = oldProject
        })
        return newProject
    }

    /**
     * 删除该项目下所有的定义
     */
    @Permission
    override fun delete(name: String) {
        repository.delete(name)
        publisher.publishEvent(QueryProjectEvent(name, QueryProjectEvent.Type.DELETE).apply { userId = userHolder.getUserId() })
    }

    override fun findByName(name: String): QueryProject? = repository.findOne(name)

    override fun findAll(): MutableList<QueryProject> = repository.findAll()

    override fun findSourceByName(name: String): QuerySource? = querySourceService.findByProjectName(name)

    override fun findBySourceId(sourceId: String) = repository.findBySourceId(sourceId)

    override fun findByDefineId(defineId: String): Set<QueryProject>? {
        return repository.findByDefineId(defineId)
    }

    override fun findByLockType(lockType: String): Set<QueryProject>? {
        return repository.findByLockType(lockType)
    }

    override fun findByDefineIdAndVersion(defineId: String, version: String): Set<QueryProject>? {
        return repository.findByDefineIdAndVersion(defineId, version)
    }

    override fun findProjectDefineVoByProjectName(projectName: String): List<ProjectDefineVo> {
        return repository.findProjectDefineVoByProjectName(projectName)
    }

    @Permission
    override fun importDefines(projectName: String, defines: List<String>) {
        publisher.publishEvent(Project2DefineEvent(Project2DefineEvent.Type.DEFINE_IMPORT, defines.mapNotNull {
            if (!Utils.isNotEmpty(project2DefineService.findByDefineIdAndProjectName(it, projectName))) {
                project2DefineService.save(Project2Define(projectName, it))
            } else {
                null
            }
        }))
    }

    @Permission
    override fun importTag(projectName: String, alias: String, tagName: String) {
        if (alias.isNotBlank() && project2DefineService.existAlias(projectName, alias))
            throw QueryException("通过标签导入的别名不能已存在")
        val defines = queryDefineService.findByTagName(tagName)
        if (defines != null && defines.isNotEmpty()) {
            project2DefineService.save(defines.map { Project2Define(projectName, it.id, alias) }.apply {
                publisher.publishEvent(Project2DefineEvent(Project2DefineEvent.Type.TAG_IMPORT, this))
            })
        }
    }

    override fun validate(projectName: String): Map<String, Any> {
        val aliasDefineNameVersionVOList = project2DefineService.findAliasDefineNameVersionVOByProjectName(projectName)
        val result = mutableMapOf<String, Any>()
        Utils.checkAndRun(aliasDefineNameVersionVOList.groupBy { it.wholeName }.map { it.key to it.value.size }.toMap().filter { it.value > 1 }, {
            it.isNotEmpty()
        }, {
            result.put("name", it)
        })
        Utils.checkAndRun(aliasDefineNameVersionVOList.filter { it.version.isBlank() }, {
            it.isNotEmpty()
        }, {
            result.put("version", it)
        })
        return result
    }

    override fun lock(projectName: String) {
        if (validate(projectName).isNotEmpty()) {
            throw QueryException("锁定失败: 项目检查中发现错误!")
        }
        val project = Utils.notNull(findByName(projectName), "找不到项目：$projectName")
        val lockType = project.lockType
        val originProject = project.copy()
        if (lockType == LockType.OPEN.name) {
            project.lockType = LockType.LOCK_RUN.name
            repository.save(project)
            publisher.publishEvent(QueryProjectEvent(project.name, QueryProjectEvent.Type.LOCK).apply {
                userId = userHolder.getUserId()
                newQueryProject = project
                originQueryProject = originProject
            })
        }
    }

    @Permission
    override fun copy(targetProjectName: String, originProjectName: String) {
        //只能对空项目进行拷贝
        if (project2DefineService.existProjectName(targetProjectName)) throw QueryException("只能进行空项目的拷贝")
        val project2DefineList = project2DefineService.findByProjectName(originProjectName)
                ?.map { specialCopy(targetProjectName, it) }
        if (!CollectionUtils.isEmpty(project2DefineList)) {
            project2DefineService.save(project2DefineList)
        }
    }

    @Permission
    override fun clean(projectName: String) {
        project2DefineService.deleteByProjectName(projectName)
    }

    override fun import(file: MultipartFile) {
        val completeProject = fromFile(file)
        dealSourceAndProject(completeProject)
        dealDefines(completeProject)
        dealExecutorObj(completeProject)
        dealProject2Define(completeProject)
    }

    override fun sync(syncProject: SyncProject) {
        //删除define、executorObj、project2Define
        TODO()
    }

    @Permission
    override fun export(projectName: String): ResponseEntity<String> {
        val project = Utils.notNull(findByName(projectName), "找不到项目:$projectName")

        val source = findSourceByName(projectName)

        val defines = queryDefineService.findByProjectName(projectName)

        val json = GsonUtils.gson.toJson(CompleteProject().let {
            it.querySource = source
            it.queryProject = project
            it.queryDefines = defines
            Utils.isNotEmptyAndThen(defines) { t: Collection<QueryDefine> ->
                it.executionObjs = executionObjService.findByDefineIds(t.map { define -> define.id }.toList())
                it.project2Defines = project2DefineService.findByProjectName(projectName)
            }
            return@let it
        })

        val headers = HttpHeaders().apply {
            contentType = MediaType.APPLICATION_JSON_UTF8
            this.setContentDispositionFormData("attachment", "$projectName.json")
            add("Pragma", "no-cache")
            add("Expires", "0")
        }
        return ResponseEntity(json, headers, HttpStatus.OK)
    }

    private fun specialCopy(projectName: String, project2Define: Project2Define): Project2Define {
        return Project2Define(projectName, project2Define.defineId, project2Define.alias).apply {
            this.version = project2Define.version
            this.remark = project2Define.remark
            this.sourceId = project2Define.sourceId
        }
    }

    private fun fromFile(file: MultipartFile): CompleteProject {
        file.originalFilename.endsWith(".json").apply {
            if (!this)
                throw QueryException("文件格式：.json")
        }
        return try {
            GsonUtils.gson.fromJson(InputStreamReader(file.inputStream), CompleteProject::class.java)!!
        } catch (e: Exception) {
            throw QueryException("文件格式错误：{querySource,queryProject,[QueryDefine]}")
        }
    }

    /**
     * 当存在source时,判断source是否相等，
     * 1> 相等，跳过
     * 2> 不相等,新增，并设置新的id
     *
     * 不存在source时，判断project中sourceId
     */
    private fun dealSourceAndProject(oneProject: CompleteProject) {
        val source = oneProject.querySource?.apply { userId = userHolder.getUserId() }
        val queryProject = oneProject.queryProject?.apply { userId = userHolder.getUserId() }
                ?: throw QueryException("该项目缺少queryProject定义")
        val dbProject = findByName(queryProject.name)
        if (source != null) {
            if (source.id.isBlank()) {
                insert(queryProject.apply {
                    sourceId = querySourceService.save(source).id
                })
            } else {
                val exist = querySourceService.findById(source.id)?.jdbcEquals(source) == true
                if (!exist) {
                    source.id = ""
                    querySourceService.save(source).id.apply {
                        queryProject.sourceId = this
                        source.id = this
                    }
                }
            }
        } else {
            if (queryProject.sourceId.isBlank() || querySourceService.findById(queryProject.sourceId) == null) throw QueryException("该项目缺少数据源定义")
        }
        if (dbProject != null) {
            if (dbProject.sourceId == queryProject.sourceId) {
                if (dbProject.userId != queryProject.userId)
                    throw QueryException("已存在名称相同、数据源相同但用户不同的项目")
            } else {
                throw QueryException("已存在名称相同但数据源不同的项目")
            }
        } else {
            insert(queryProject)
        }
    }

    private fun dealDefines(oneProject: CompleteProject) {
        val queryDefines = oneProject.queryDefines
        Utils.isNotEmptyAndThen(queryDefines) {
            it.forEach { qd: QueryDefine ->
                qd.userId = userHolder.getUserId()
            }
            queryDefineService.save(it).apply {
                logger.info { "导入定义对象数量：${it.size}" }
            }
        }
    }

    private fun dealExecutorObj(oneProject: CompleteProject) {
        val executionObjs = oneProject.executionObjs
        Utils.isNotEmptyAndThen(executionObjs) {
            it.forEach { obj: ExecutionObj ->
                obj.userId = userHolder.getUserId()
            }
            executionObjService.save(it).apply {
                logger.info { "导入执行对象数量：${it.size}" }
            }
        }
    }

    private fun dealProject2Define(oneProject: CompleteProject) {
        val project2DefineList = oneProject.project2Defines
        Utils.isNotEmptyAndThen(project2DefineList) {
            it.forEach { pd: Project2Define ->
                pd.id = null
                pd.userId = userHolder.getUserId()
            }
            project2DefineService.save(it).apply {
                logger.info { "导入项目关联定义对象数量：${it.size}" }
            }
        }
    }

    override fun setUserInfoHolder(userInfoHolder: UserInfoHolder) {
        userHolder = userInfoHolder
    }

    override fun setRunnerJdbcTemplate(jdbcTemplate: JdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate
    }

    override fun adapter(methodName: String, args: Array<Any?>): PermissionObject? {
        return when (methodName) {
            "save" -> {
                val queryProject = args[0] as QueryProject
                Utils.checkAndRun(findByName(queryProject.name), {
                    it == null
                }, {
                    PermissionObject(it!!.name).apply {
                        type = "queryProject"
                        userId = it.userId
                        allowTheSameUserToOperate = true
                    }
                })
            }
            "delete", "importDefines", "importTag", "copy", "clean", "export" -> {
                PermissionObject(args[0] as String).apply {
                    type = "queryProject"
                }
            }
            else -> null
        }
    }
}